<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>最新版本</version>
</dependency>
引入 MyBatis-Plus 之后不要再次引入MyBatis以及MyBatis-Spring,以避免因版本差异导致的问题。
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
public interface UserMapper extends BaseMapper<User> {
}
@SpringBootTest
public class SampleTest {
@Autowired
private UserMapper userMapper;
@Test
public void testSelect() {
System.out.println(("----- selectAll method test ------"));
List<User> userList = userMapper.selectList(null);
Assert.isTrue(5 == userList.size(), "");
userList.forEach(System.out::println);
}
}
UserMapper 中的 selectList() 方法的参数为 MP 内置的条件封装器 Wrapper,所以不填写就是无任何条件
说明:
get 查询单行 remove 删除 list 查询集合 page 分页 前缀命名方式区分 Mapper 层避免混淆,T 为任意实体对象IBaseService 继承 Mybatis-Plus 提供的基类Wrapper 为 条件构造器
public interface UserService extends IService<User> {
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);
| 类型 | 参数名 | 描述 |
|---|---|---|
| T | entity | 实体对象 |
| Collection | entityList | 实体对象集合 |
| int | batchSize | 插入批次数量 |
// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
| 类型 | 参数名 | 描述 |
|---|---|---|
| T | entity | 实体对象 |
| Wrapper | updateWrapper | 实体对象封装操作类 UpdateWrapper |
| Collection | entityList | 实体对象集合 |
| int | batchSize | 插入批次数量 |
// 根据 queryWrapper 设置的条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);
| 类型 | 参数名 | 描述 |
|---|---|---|
| Wrapper | queryWrapper | 实体包装类 QueryWrapper |
| Serializable | id | 主键 ID |
| Map<String, Object> | columnMap | 表字段 map 对象 |
| Collection<? extends Serializable> | idList | 主键 ID 列表 |
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);
| 类型 | 参数名 | 描述 |
|---|---|---|
| Wrapper | updateWrapper | 实体对象封装操作类 UpdateWrapper |
| T | entity | 实体对象 |
| Collection | entityList | 实体对象集合 |
| int | batchSize | 更新批次数量 |
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
| 类型 | 参数名 | 描述 |
|---|---|---|
| Serializable | id | 主键 ID |
| Wrapper | queryWrapper | 实体对象封装操作类 QueryWrapper |
| boolean | throwEx | 有多个 result 是否抛出异常 |
| T | entity | 实体对象 |
| Function<? super Object, V> | mapper | 转换函数 |
// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
| 类型 | 参数名 | 描述 |
|---|---|---|
| Wrapper | queryWrapper | 实体对象封装操作类 QueryWrapper |
| Collection<? extends Serializable> | idList | 主键 ID 列表 |
| Map<String, Object> | columnMap | 表字段 map 对象 |
| Function<? super Object, V> | mapper | 转换函数 |
// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
| 类型 | 参数名 | 描述 |
|---|---|---|
| IPage | page | 翻页对象 |
| Wrapper | queryWrapper | 实体对象封装操作类 QueryWrapper |
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);
| 类型 | 参数名 | 描述 |
|---|---|---|
| Wrapper | queryWrapper | 实体对象封装操作类 QueryWrapper |
// 链式查询 普通
QueryChainWrapper<T> query();
// 链式查询 lambda 式。注意:不支持 Kotlin
LambdaQueryChainWrapper<T> lambdaQuery();
// 示例:
query().eq("column", value).one();
lambdaQuery().eq(Entity::getId, value).list();
// 链式更改 普通
UpdateChainWrapper<T> update();
// 链式更改 lambda 式。注意:不支持 Kotlin
LambdaUpdateChainWrapper<T> lambdaUpdate();
// 示例:
update().eq("column", value).remove();
lambdaUpdate().eq(Entity::getId, value).update(entity);
说明:
Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器T 为任意实体对象Serializable 为任意类型主键 Mybatis-Plus 不推荐使用复合主键约定每一张表都有自己的唯一 id 主键Wrapper 为 条件构造器@Mapper
public interface UserMapper extends BaseMapper<User> {
}
// 插入一条记录
int insert(T entity);
| 类型 | 参数名 | 描述 |
|---|---|---|
| T | entity | 实体对象 |
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 ID 删除
int deleteById(Serializable id);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
| 类型 | 参数名 | 描述 |
|---|---|---|
| Wrapper | wrapper | 实体对象封装操作类(可以为 null) |
| Collection<? extends Serializable> | idList | 主键 ID 列表(不能为 null 以及 empty) |
| Serializable | id | 主键 ID |
| Map<String, Object> | columnMap | 表字段 map 对象 |
// 根据 whereWrapper 条件,更新记录
int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);
// 根据 ID 修改
int updateById(@Param(Constants.ENTITY) T entity);
使用提示:
在调用updateById方法前,需要在T entity(对应的实体类)中的主键属性上加上@TableId注解。
| 类型 | 参数名 | 描述 |
|---|---|---|
| T | entity | 实体对象 (set 条件值,可为 null) |
| Wrapper | updateWrapper | 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句) |
// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
| 类型 | 参数名 | 描述 |
|---|---|---|
| Serializable | id | 主键 ID |
| Wrapper | queryWrapper | 实体对象封装操作类(可以为 null) |
| Collection<? extends Serializable> | idList | 主键 ID 列表(不能为 null 以及 empty) |
| Map<String, Object> | columnMap | 表字段 map 对象 |
| IPage | page | 分页查询条件(可以为 RowBounds.DEFAULT) |
说明:选装件位于 com.baomidou.mybatisplus.extension.injector.methods 包下 需要配合Sql 注入器使用,案例(opens new window)使用详细见源码注释
int alwaysUpdateSomeColumnById(T entity);
int insertBatchSomeColumn(List<T> entityList);
int logicDeleteByIdWithFill(T entity);
说明:
class User extends Model<User>{
// fields...
}
CRUD方法(演示部分api,仅供参考)User user = new User();
user.insert();
user.selectAll();
user.updateById();
user.deleteById();
// ...
说明:
selectList查询后的结果用Stream流进行了一些封装,使其可以返回一些指定结果,简洁了api的调用peeks,其类型为Consumer...,可一直往后叠加操作例如:List<Long> ids = SimpleQuery.list(Wrappers.lambdaQuery(), Entity::getId, System.out::println, user -> userNames.add(user.getName()));// 查询表内记录,封装返回为Map<属性,实体>
Map<A, E> keyMap(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, Consumer<E>... peeks);
// 查询表内记录,封装返回为Map<属性,实体>,考虑了并行流的情况
Map<A, E> keyMap(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, boolean isParallel, Consumer<E>... peeks);
| 类型 | 参数名 | 描述 |
|---|---|---|
| E | entity | 实体对象 |
| A | attribute | 实体属性类型,也是map中key的类型 |
| LambdaQuery | wrapper | 支持lambda的条件构造器 |
| SFunction<E, A> | sFunction | 实体中属性的getter,用于封装后map中作为key的条件 |
| boolean | isParallel | 为true时底层使用并行流执行 |
| Consumer... | peeks | 可叠加的后续操作 |
// 查询表内记录,封装返回为Map<属性,属性>
Map<A, P> map(LambdaQueryWrapper<E> wrapper, SFunction<E, A> keyFunc, SFunction<E, P> valueFunc, Consumer<E>... peeks);
// 查询表内记录,封装返回为Map<属性,属性>,考虑了并行流的情况
Map<A, P> map(LambdaQueryWrapper<E> wrapper, SFunction<E, A> keyFunc, SFunction<E, P> valueFunc, boolean isParallel, Consumer<E>... peeks);
| 类型 | 参数名 | 描述 |
|---|---|---|
| E | entity | 实体对象 |
| A | attribute | 实体属性类型,也是map中key的类型 |
| P | attribute | 实体属性类型,也是map中value的类型 |
| LambdaQueryWrapper | wrapper | 支持lambda的条件构造器 |
| SFunction<E, A> | keyFunc | 封装后map中作为key的条件 |
| SFunction<E, P> | valueFunc | 封装后map中作为value的条件 |
| boolean | isParallel | 为true时底层使用并行流执行 |
| Consumer... | peeks | 可叠加的后续操作 |
// 查询表内记录,封装返回为Map<属性,List<实体>>
Map<K, List<T>> group(LambdaQueryWrapper<T> wrapper, SFunction<T, A> sFunction, Consumer<T>... peeks);
// 查询表内记录,封装返回为Map<属性,List<实体>>,考虑了并行流的情况
Map<K, List<T>> group(LambdaQueryWrapper<T> wrapper, SFunction<T, K> sFunction, boolean isParallel, Consumer<T>... peeks);
// 查询表内记录,封装返回为Map<属性,分组后对集合进行的下游收集器>
M group(LambdaQueryWrapper<T> wrapper, SFunction<T, K> sFunction, Collector<? super T, A, D> downstream, Consumer<T>... peeks);
// 查询表内记录,封装返回为Map<属性,分组后对集合进行的下游收集器>,考虑了并行流的情况
M group(LambdaQueryWrapper<T> wrapper, SFunction<T, K> sFunction, Collector<? super T, A, D> downstream, boolean isParallel, Consumer<T>... peeks);
| 类型 | 参数名 | 描述 |
|---|---|---|
| T | entity | 实体对象 |
| K | attribute | 实体属性类型,也是map中key的类型 |
| D | - | 下游收集器返回类型,也是map中value的类型 |
| A | - | 下游操作中间类型 |
| M | - | 最终结束返回的Map<K, D> |
| LambdaQueryWrapper | wrapper | 支持lambda的条件构造器 |
| SFunction<E, A> | sFunction | 分组依据,封装后map中作为key的条件 |
| Collector<T, A, D> | downstream | 下游收集器 |
| boolean | isParallel | 为true时底层使用并行流执行 |
| Consumer... | peeks | 可叠加的后续操作 |
// 查询表内记录,封装返回为List<属性>
List<A> list(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, Consumer<E>... peeks);
// 查询表内记录,封装返回为List<属性>,考虑了并行流的情况
List<A> list(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, boolean isParallel, Consumer<E>... peeks);
| 类型 | 参数名 | 描述 |
|---|---|---|
| E | entity | 实体对象 |
| A | attribute | 实体属性类型,也是list中元素的类型 |
| LambdaQueryWrapper | wrapper | 支持lambda的条件构造器 |
| SFunction<E, A> | sFunction | 封装后list中的元素 |
| boolean | isParallel | 为true时底层使用并行流执行 |
| Consumer... | peeks | 可叠加的后续操作 |
说明:
Spring环境下Service循环注入、简洁代码,提升效率例如:
// 根据id查询
List<Entity> list = Db.listByIds(Arrays.asList(1L, 2L), Entity.class);
// 根据条件构造器查询
List<Entity> list = Db.list(Wrappers.lambdaQuery(Entity.class));
// 批量根据id更新
boolean isSuccess = Db.updateBatchById(list);
表名 user → 实体名 User
字段名 name → 属性名 name
字段名 user_email → 实体类属性名 userEmail
配置开启
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
在实体类上使用@TableName注解
mybatis-plus:
global-config:
db-config:
table-prefix: prefix
在类成员属性上使用@TableField
如果属性刚好关键字重合,sql会有错误,用@TableField把字段用``包起来
@TableField(select = false)
@TableField(exist = false)
数据库内设置主键自增配合该策略使用
必须手动插入id,否则无法添加
1.第一位 占用1bit,其值始终是0,没有实际作用。
2.时间戳 占用41bit,精确到毫秒,总共可以容纳约69年的时间。
3.工作机器id 占用10bit,其中高位5bit是数据中心ID,低位5bit是工作节点ID,做多可以容纳1024个节点。
4.序列号 占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生4096个ID。
不指定,跟随全局策略,全局默认用ASSIGN_ID
mybatis-plus:
global-config:
db-config:
id-type: auto
生成32位UUID作为主键,要求主键为varchar
1~8位采用系统时间,在系统时间上精确到毫秒级保证时间上的唯一性;
9~16位采用底层的IP地址,在服务器集群中的唯一性;
17~24位采用当前对象的HashCode值,在一个内部对象上的唯一性;
25~32位采用调用方法的一个随机数,在一个对象内的毫秒级的唯一性。
字符串方式表示字段
QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
userQueryWrapper.eq("name","Jack");
List<User> userList = userMapper.selectList(userQueryWrapper);
方法引用方式表示字段
LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
userLambdaQueryWrapper.eq(User::getName,"Jack");
List<User> userList = userMapper.selectList(userLambdaQueryWrapper);
说明:
boolean condition表示该条件是否加入最后生成的sql中,例如:query.like(StringUtils.isNotBlank(name), Entity::getName, name) .eqgetAge, ageboolean类型的入参,默认为trueParam均为Wrapper的子类实例(均具有AbstractWrapper的所有方法)R为泛型,在普通wrapper中是String,在LambdaWrapper中是函数getId,Entity为实体类,getId为字段id的getter MethodR column均表示数据库字段,当R具体类型为String时则为数据库字段名(字段名是数据库关键字的自己用转义符包裹!)!而不是实体类数据字段名!!!,另当R具体类型为SFunction时项目runtime不支持eclipse自家的编译器!!!Map和List的均以json形式表现!Map或者List为空,则不会加入最后生成的sql中!!!警告:不支持以及不赞成在 RPC 调用中把 Wrapper 进行传输
说明:
QueryWrapper(LambdaQueryWrapper) 和 UpdateWrapper(LambdaUpdateWrapper) 的父类
用于生成 sql 的 where 条件, entity 属性也用于生成 sql 的 where 条件
注意: entity 生成的 where 条件与 使用各个 api 生成的 where 条件没有任何关联行为
allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)
全部eq(或个别isNull)个别参数说明:
params : key为数据库字段名,value为字段值null2IsNull : 为true则在map的value为null时调用 isNull 方法,为false时则忽略value为null的
例1: allEq({id:1,name:"老王",age:null})--->id = 1 and name = '老王' and age is null
例2: allEq({id:1,name:"老王",age:null}, false)--->id = 1 and name = '老王'
allEq(BiPredicate<R, V> filter, Map<R, V> params)
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
个别参数说明:
filter : 过滤函数,是否允许字段传入比对条件中params 与 null2IsNull : 同上
allEq((k,v) -> k.contains("a"), {id:1,name:"老王",age:null})--->name = '老王' and age is nullallEq((k,v) -> k.contains("a"), {id:1,name:"老王",age:null}, false)--->name = '老王'eq(R column, Object val)
eq(boolean condition, R column, Object val)
eq("name", "老王")--->name = '老王'ne(R column, Object val)
ne(boolean condition, R column, Object val)
ne("name", "老王")--->name <> '老王'gt(R column, Object val)
gt(boolean condition, R column, Object val)
gt("age", 18)--->age > 18ge(R column, Object val)
ge(boolean condition, R column, Object val)
ge("age", 18)--->age >= 18lt(R column, Object val)
lt(boolean condition, R column, Object val)
lt("age", 18)--->age < 18le(R column, Object val)
le(boolean condition, R column, Object val)
le("age", 18)--->age <= 18between(R column, Object val1, Object val2)
between(boolean condition, R column, Object val1, Object val2)
between("age", 18, 30)--->age between 18 and 30notBetween(R column, Object val1, Object val2)
notBetween(boolean condition, R column, Object val1, Object val2)
notBetween("age", 18, 30)--->age not between 18 and 30like(R column, Object val)
like(boolean condition, R column, Object val)
like("name", "王")--->name like '%王%'notLike(R column, Object val)
notLike(boolean condition, R column, Object val)
notLike("name", "王")--->name not like '%王%'likeLeft(R column, Object val)
likeLeft(boolean condition, R column, Object val)
likeLeft("name", "王")--->name like '%王'likeRight(R column, Object val)
likeRight(boolean condition, R column, Object val)
likeRight("name", "王")--->name like '王%'notLikeLeft(R column, Object val)
notLikeLeft(boolean condition, R column, Object val)
notLikeLeft("name", "王")--->name not like '%王'notLikeRight(R column, Object val)
notLikeRight(boolean condition, R column, Object val)
notLikeRight("name", "王")--->name not like '王%'isNull(R column)
isNull(boolean condition, R column)
isNull("name")--->name is nullisNotNull(R column)
isNotNull(boolean condition, R column)
isNotNull("name")--->name is not nullin(R column, Collection<?> value)
in(boolean condition, R column, Collection<?> value)
in("age",{1,2,3})--->age in (1,2,3)in(R column, Object... values)
in(boolean condition, R column, Object... values)
in("age", 1, 2, 3)--->age in (1,2,3)notIn(R column, Collection<?> value)
notIn(boolean condition, R column, Collection<?> value)
notIn("age",{1,2,3})--->age not in (1,2,3)notIn(R column, Object... values)
notIn(boolean condition, R column, Object... values)
notIn("age", 1, 2, 3)--->age not in (1,2,3)inSql(R column, String inValue)
inSql(boolean condition, R column, String inValue)
inSql("age", "1,2,3,4,5,6")--->age in (1,2,3,4,5,6)inSql("id", "select id from table where id < 3")--->id in (select id from table where id < 3)notInSql(R column, String inValue)
notInSql(boolean condition, R column, String inValue)
notInSql("age", "1,2,3,4,5,6")--->age not in (1,2,3,4,5,6)notInSql("id", "select id from table where id < 3")--->id not in (select id from table where id < 3)groupBy(R... columns)
groupBy(boolean condition, R... columns)
groupBy("id", "name")--->group by id,nameorderByAsc(R... columns)
orderByAsc(boolean condition, R... columns)
orderByAsc("id", "name")--->order by id ASC,name ASCorderByDesc(R... columns)
orderByDesc(boolean condition, R... columns)
orderByDesc("id", "name")--->order by id DESC,name DESCorderBy(boolean condition, boolean isAsc, R... columns)
orderBy(true, true, "id", "name")--->order by id ASC,name ASChaving(String sqlHaving, Object... params)
having(boolean condition, String sqlHaving, Object... params)
having("sum(age) > 10")--->having sum(age) > 10having("sum(age) > {0}", 11)--->having sum(age) > 11func(Consumer<Children> consumer)
func(boolean condition, Consumer<Children> consumer)
func(i -> if(true) {i.eq("id", 1)} else {i.ne("id", 1)})or()
or(boolean condition)
拼接 OR
注意事项:
主动调用or表示紧接着下一个方法不是用and连接!(不调用or则默认为使用and连接)
例: eq("id",1).or().eq("name","老王")--->id = 1 or name = '老王'
or(Consumer<Param> consumer)
or(boolean condition, Consumer<Param> consumer)
or(i -> i.eq("name", "李白").ne("status", "活着"))--->or (name = '李白' and status <> '活着')and(Consumer<Param> consumer)
and(boolean condition, Consumer<Param> consumer)
and(i -> i.eq("name", "李白").ne("status", "活着"))--->and (name = '李白' and status <> '活着')nested(Consumer<Param> consumer)
nested(boolean condition, Consumer<Param> consumer)
nested(i -> i.eq("name", "李白").ne("status", "活着"))--->(name = '李白' and status <> '活着')apply(String applySql, Object... params)
apply(boolean condition, String applySql, Object... params)
拼接 sql
注意事项:
该方法可用于数据库函数 动态入参的params对应前面applySql内部的{index}部分.这样是不会有sql注入风险的,反之会有!
例: apply("id = 1")--->id = 1
例: apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")--->date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
例: apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08")--->date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
last(String lastSql)
last(boolean condition, String lastSql)
无视优化规则直接拼接到 sql 的最后
注意事项:
只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用
例: last("limit 1")
exists(String existsSql)
exists(boolean condition, String existsSql)
exists("select id from table where age = 1")--->exists (select id from table where age = 1)notExists(String notExistsSql)
notExists(boolean condition, String notExistsSql)
notExists("select id from table where age = 1")--->not exists (select id from table where age = 1)说明:
继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件
及 LambdaQueryWrapper, 可以通过 new QueryWrapper().lambda() 方法获取
select(String... sqlSelect)
select(Predicate<TableFieldInfo> predicate)
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)
设置查询字段
说明:
以上方法分为两类.
第二类方法为:过滤查询字段(主键除外),入参不包含 class 的调用前需要wrapper内的entity属性有值! 这两类方法重复调用以最后一次为准
例: select("id", "name", "age")
例: select(i -> i.getProperty().startsWith("test"))
说明:
继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件
及 LambdaUpdateWrapper, 可以通过 new UpdateWrapper().lambda() 方法获取!
set(String column, Object val)
set(boolean condition, String column, Object val)
set("name", "老李头")set("name", "")--->数据库字段值变为空字符串set("name", null)--->数据库字段值变为nullsetSql(String sql)
setSql("name = '老李头'")LambdaWrapperQueryWrapper中是获取LambdaQueryWrapperUpdateWrapper中是获取LambdaUpdateWrapper注意事项:
需要mybatis-plus版本 >= 3.0.7 param 参数名要么叫ew,要么加上注解@Param(Constants.WRAPPER) 使用${ew.customSqlSegment} 不支持 Wrapper 内的entity生成where语句
由于kotlin相比于java多了数据对象(data class),在未说明情况下可能会混用。建议按照以下形式定义持久化对象
@TableName("sys_user")
class User {
@TableId(type = IdType.AUTO)
var id: Int? = null
@TableField("username")
var name: String? = null
var roleId: Int? = null
}
注意:这里的TableId及TableField并非必要,只是为了展示Mybatis-Plus中的annotation使用
这里所有成员都需要定义为可空类型(?),并赋予null的初始值,方便我们在以下场景中使用(类似java中的updateSelective)
val wrapper = KtUpdateWrapper(User::class.java).eq(User::id, 2)
val newRecord = User()
newRecord.name = "newName"
userMapper!!.update(newRecord, wrapper)
不建议使用data class及全参数构造方法,这样我们会写很多不必要的null来构造一个空对象
@Select("select * from mysql_data ${ew.customSqlSegment}")
List<MysqlData> getAll(@Param(Constants.WRAPPER) Wrapper wrapper);
List<MysqlData> getAll(Wrapper ew);
<select id="getAll" resultType="MysqlData">
SELECT * FROM mysql_data ${ew.customSqlSegment}
</select>
kotlin 可以使用
QueryWrapper和UpdateWrapper但无法使用LambdaQueryWrapper和LambdaUpdateWrapper
如果想使用 lambda 方式的 wrapper 请使用KtQueryWrapper和KtUpdateWrapper
val queryWrapper = KtQueryWrapper(User()).eq(User::name, "sss").eq(User::roleId, "sss2")
userMapper!!.selectList(queryWrapper)
val updateConditionWrapper = KtUpdateWrapper(User()).eq(User::name, "sss").eq(User::roleId, "sss2")
val updateRecord = User()
updateRecord.name = "newName"
userMapper!!.update(updateRecord, updateConditionWrapper)
val updateRecord = User()
updateRecord.id = 2
updateRecord.name = "haha"
userMapper.updateById(updateRecord)
// 区分:
// 链式调用 普通
UpdateChainWrapper<T> update();
// 链式调用 lambda 式。注意:不支持 Kotlin
LambdaUpdateChainWrapper<T> lambdaUpdate();
// 等价示例:
query().eq("id", value).one();
lambdaQuery().eq(Entity::getId, value).one();
// 等价示例:
update().eq("id", value).remove();
lambdaUpdate().eq(Entity::getId, value).remove();
只对自动注入的 sql 起效:
例如:
update user set deleted=1 where id = 1 and deleted=0select id,name,deleted from user where deleted=0字段类型支持说明:
Integer,Boolean,LocalDateTime)datetime,逻辑未删除值和已删除值支持配置为字符串null,另一个值支持配置为函数来获取值如now()附录:
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
字段上加注解@TableLogic()
@TableLogic(value = "1",delval = "0")
private Integer status;
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
使用同一个对象进行插入会导致主键重复,构造两次即可解决
User user = null;
for (int i = 0; i < 10000; i++) {
user = new User();
user.setName("name" + i);
user.setAge(i);
user.setEmail("email" + i);
userMapper.insert(user);
}
在使用的枚举类字段上加@EnumValue
在项目中有一些属性,如果我们不希望每次都填充的话,我们可以设置为自动填充,比如创建时间和更新时间可以设置为自动填充。
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
@TableField(fill = FieldFill.UPDATE)
private LocalDateTime updateTime;
自定义处理器
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
// 或者
this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// 或者
this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
// 或者
this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// 或者
this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
}
@TableName("sys_user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| value | String | 否 | "" | 表名 |
| schema | String | 否 | "" | schema |
| keepGlobalPrefix | boolean | 否 | false | 是否保持使用全局的 tablePrefix 的值(当全局 tablePrefix 生效时) |
| resultMap | String | 否 | "" | xml 中 resultMap 的 id(用于满足特定类型的实体类对象绑定) |
| autoResultMap | boolean | 否 | false | 是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建与注入) |
| excludeProperty | String[] | 否 | {} | 需要排除的属性名 @since 3.3.1 |
关于 autoResultMap 的说明:
MP 会自动构建一个 resultMap 并注入到 MyBatis 里(一般用不上),请注意以下内容:
因为 MP 底层是 MyBatis,所以 MP 只是帮您注入了常用 CRUD 到 MyBatis 里,注入之前是动态的(根据您的 Entity 字段以及注解变化而变化),但是注入之后是静态的(等于 XML 配置中的内容)。
而对于 typeHandler 属性,MyBatis 只支持写在 2 个地方:
insert 和 update 语句的 #{property} 中的 property 后面(例:#{property,typehandler=xxx.xxx.xxx}),并且只作用于当前 设置值除了以上两种直接指定 typeHandler 的形式,MyBatis 有一个全局扫描自定义 typeHandler 包的配置,原理是根据您的 property 类型去找其对应的 typeHandler 并使用。
@TableName("sys_user")
public class User {
@TableId
private Long id;
private String name;
private Integer age;
private String email;
}
| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| value | String | 否 | "" | 主键字段名 |
| type | Enum | 否 | IdType.NONE | 指定主键类型 |
| 值 | 描述 |
|---|---|
| AUTO | 数据库 ID 自增 |
| NONE | 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) |
| INPUT | insert 前自行 set 主键值 |
| ASSIGN_ID | 分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法) |
| ASSIGN_UUID | 分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认 default 方法) |
分布式全局唯一 ID 长整型类型(please use ASSIGN_ID) | |
32 位 UUID 字符串(please use ASSIGN_UUID) | |
分布式全局唯一 ID 字符串类型(please use ASSIGN_ID) |
描述:字段注解(非主键)
@TableName("sys_user")
public class User {
@TableId
private Long id;
@TableField("nickname")
private String name;
private Integer age;
private String email;
}
| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| value | String | 否 | "" | 数据库字段名 |
| exist | boolean | 否 | true | 是否为数据库表字段 |
| condition | String | 否 | "" | 字段 where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的 %s=#{%s},参考 (opens new window) |
| update | String | 否 | "" | 字段 update set 部分注入,例如:当在version字段上注解update="%s+1" 表示更新时会 set version=version+1 (该属性优先级高于 el 属性) |
| insertStrategy | Enum | 否 | FieldStrategy.DEFAULT | 举例:NOT_NULL insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>) |
| updateStrategy | Enum | 否 | FieldStrategy.DEFAULT | 举例:IGNORED update table_a set column=#{columnProperty} |
| whereStrategy | Enum | 否 | FieldStrategy.DEFAULT | 举例:NOT_EMPTY where <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if> |
| fill | Enum | 否 | FieldFill.DEFAULT | 字段自动填充策略 |
| select | boolean | 否 | true | 是否进行 select 查询 |
| keepGlobalFormat | boolean | 否 | false | 是否保持使用全局的 format 进行处理 |
| jdbcType | JdbcType | 否 | JdbcType.UNDEFINED | JDBC 类型 (该默认值不代表会按照该值生效) |
| typeHandler | Class<? extends TypeHandler> | 否 | UnknownTypeHandler.class | 类型处理器 (该默认值不代表会按照该值生效) |
| numericScale | String | 否 | "" | 指定小数点后保留的位数 |
关于`jdbcType`和`typeHandler`以及`numericScale`的说明:
numericScale只生效于 update 的 sql. jdbcType和typeHandler如果不配合@TableName#autoResultMap = true一起使用,也只生效于 update 的 sql. 对于typeHandler如果你的字段类型和 set 进去的类型为equals关系,则只需要让你的typeHandler让 Mybatis 加载到即可,不需要使用注解
| 值 | 描述 |
|---|---|
| IGNORED | 忽略判断 |
| NOT_NULL | 非 NULL 判断 |
| NOT_EMPTY | 非空判断(只对字符串类型字段,其他类型字段依然为非 NULL 判断) |
| DEFAULT | 追随全局配置 |
| NEVER | 不加入SQL |
| 值 | 描述 |
|---|---|
| DEFAULT | 默认不处理 |
| INSERT | 插入时填充字段 |
| UPDATE | 更新时填充字段 |
| INSERT_UPDATE | 插入和更新时填充字段 |
@Version 在字段上| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| value | String | 否 | "" | 逻辑未删除值 |
| delval | String | 否 | "" | 逻辑删除值 |
oracle| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| value | String | 否 | "" | 序列名 |
| dbType | Enum | 否 | DbType.OTHER | 数据库类型,未配置默认使用注入 IKeyGenerator 实现,多个实现必须指定 |
value 值为 1 | yes | on 视为忽略,例如 @InterceptorIgnore(tenantLine = "1")value 值为 0 | false | off | 空值不变 视为正常执行。see 插件主体
| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| isDesc | boolean | 否 | true | 是否倒序查询 |
| sort | short | 否 | Short.MAX_VALUE | 数字越小越靠前 |
自动导入 MyBatis-Plus 测试所需相关配置,通过 @MybatisPlusTest 注解快速配置测试类。
Maven:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter-test</artifactId>
<version>3.5.3.2</version>
</dependency>
Gradle:
compile group: 'com.baomidou', name: 'mybatis-plus-boot-starter-test', version: '3.5.3.2'
通过 @MybatisPlusTest 可快速编写 Mapper 对应的测试类,实现快速测试代码
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import static org.assertj.core.api.Assertions.assertThat;
@MybatisPlusTest
class MybatisPlusSampleTest {
@Autowired
private SampleMapper sampleMapper;
@Test
void testInsert() {
Sample sample = new Sample();
sampleMapper.insert(sample);
assertThat(sample.getId()).isNotNull();
}
}
mybatis-plus:
......
configuration:
......
global-config:
......
db-config:
......
StringnullMyBatis 配置文件位置,如果您有单独的 MyBatis 配置,请将其路径配置到 configLocation 中.MyBatis Configuration 的具体内容请参考MyBatis 官方文档 (opens new window)
String[]["classpath*:/mapper/**/*.xml"]MyBatis Mapper 所对应的 XML 文件位置,如果您在 Mapper 中有自定义方法(XML 中有自定义实现),需要进行该配置,告诉 Mapper 所对应的 XML 文件位置
注意
Maven 多模块项目的扫描路径需以 classpath*: 开头 (即加载多个 jar 包下的 XML 文件)
StringnullMyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名,注册后在 Mapper 对应的 XML 文件中可以直接使用类名,而不用使用全限定的类名(即 XML 中调用的时候不用包含包名)
Class<?>null该配置请和 typeAliasesPackage 一起使用,如果配置了该属性,则仅仅会扫描路径下以该类作为父类的域对象
StringnullTypeHandler 扫描路径,如果配置了该属性,SqlSessionFactoryBean 会把该包下面的类注册为对应的 TypeHandler
提示
TypeHandler 通常用于自定义类型转换。
Stringnull枚举类 扫描路径,如果配置了该属性,会将路径下的枚举类进行注入,让实体类字段能够简单快捷的使用枚举属性
注意
从 3.5.2 开始该配置无效,无需要配置即可使用 "通用枚举" 功能
booleanfalse启动时是否检查 MyBatis XML 文件的存在,默认不检查
ExecutorTypesimple通过该属性可指定 MyBatis 的执行器,MyBatis 的执行器总共有三种:
Propertiesnull指定外部化 MyBatis Properties 配置,通过该配置可以抽离配置,实现不同环境的配置部署
Configurationnull原生 MyBatis 所支持的配置,具体请查看 Configuration
com.baomidou.mybatisplus.core.config.GlobalConfigGlobalConfig::newMyBatis-Plus 全局策略配置,具体请查看 GlobalConfig
本部分(Configuration)的配置大都为 MyBatis 原生支持的配置,这意味着您可以通过 MyBatis XML 配置文件的形式进行配置。
booleantrue是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射。
注意
此属性在 MyBatis 中原默认值为 false,在 MyBatis-Plus 中,此属性也将用于生成最终的 SQL 的 select body
如果您的数据库命名符合规则无需使用 @TableField 注解指定数据库字段名
Class<? extends TypeHandler>org.apache.ibatis.type.EnumTypeHandler默认枚举处理类,如果配置了该属性,枚举将统一使用指定处理器进行处理
提示
注意
从 3.5.2 开始 默认枚举处理器为 CompositeEnumTypeHandler 会对定义为mp通用枚举的枚举(实现IEnum了或加了EnumValue注解) 在内部使用MybatisEnumTypeHandler处理枚举 其他的枚举使用内部属性 defaultEnumTypeHandler(默认为org.apache.ibatis.type.EnumTypeHandler)进行处理 此配置仅改变 CompositeEnumTypeHandler#defaultEnumTypeHandler的值
booleantrue当设置为 true 的时候,懒加载的对象可能被任何懒属性全部加载,否则,每个属性都按需加载。需要和 lazyLoadingEnabled 一起使用。
AutoMappingBehaviorpartialMyBatis 自动映射策略,通过该配置可指定 MyBatis 是否并且如何来自动映射数据表字段与对象的属性,总共有 3 种可选值:
AutoMappingUnknownColumnBehaviorNONEMyBatis 自动映射时未知列或未知属性处理策略,通过该配置可指定 MyBatis 在自动映射过程中遇到未知列或者未知属性时如何处理,总共有 3 种可选值:
StringSESSIONMybatis 一级缓存,默认为 SESSION。
单服务架构中(有且仅有只有一个程序提供相同服务),一级缓存开启不会影响业务,只会提高性能。 微服务架构中需要关闭一级缓存,原因:Service1 先查询数据,若之后 Service2 修改了数据,之后 Service1 又再次以同样的查询条件查询数据,因走缓存会出现查处的数据不是最新数据
booleantrue开启 Mybatis 二级缓存,默认为 true。
booleanfalse指定当结果集中值为 null 的时候是否调用映射对象的 Setter(Map 对象时为 put)方法,通常运用于有 Map.keySet() 依赖或 null 值初始化的情况。
通俗的讲,即 MyBatis 在使用 resultMap 来映射查询结果中的列,如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段,这就导致在调用到该字段的时候由于没有映射,取不到而报空指针异常。
当您遇到类似的情况,请针对该属性进行相关配置以解决以上问题。
注意
基本类型(int、boolean 等)是不能设置成 null 的。
Class<?>null指定一个提供 Configuration 实例的工厂类。该工厂生产的实例将用来加载已经被反序列化对象的懒加载属性值,其必须包含一个签名方法static Configuration getConfiguration()。(从 3.2.3 版本开始)
booleantrue是否控制台 print mybatis-plus 的 LOGO
booleanfalse是否初始化 SqlRunner(com.baomidou.mybatisplus.extension.toolkit.SqlRunner)
com.baomidou.mybatisplus.core.injector.ISqlInjectorcom.baomidou.mybatisplus.core.injector.DefaultSqlInjectorSQL 注入器(starter 下支持@bean注入)
Classcom.baomidou.mybatisplus.core.mapper.Mapper.class通用 Mapper 父类(影响 sqlInjector,只有这个的子类的 mapper 才会注入 sqlInjector 内的 method)
com.baomidou.mybatisplus.core.handlers.MetaObjectHandlernull元对象字段填充控制器(starter 下支持@bean注入)
com.baomidou.mybatisplus.core.incrementer.IdentifierGeneratorcom.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGeneratorId 生成器(starter 下支持@bean注入)
com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfignullMyBatis-Plus 全局策略中的 DB 策略配置,具体请查看 DbConfig
com.baomidou.mybatisplus.annotation.IdTypeASSIGN_ID全局默认主键类型
Stringnull表名前缀
Stringnullschema
Stringnull字段 format,例: %s,(对主键无效)
Stringnull字段 format,例: %s
Stringnullentity 的字段(property)的 format,只有在 column as property 这种情况下生效例: %s,(对主键无效)
booleantrue表名是否使用驼峰转下划线命名,只对表名生效
booleanfalse大写命名,对表名和字段名均生效
com.baomidou.mybatisplus.core.incrementer.IKeyGeneratornull表主键生成器(starter 下支持@bean注入)
Stringnull全局的 entity 的逻辑删除字段属性名,(逻辑删除下有效)
String1逻辑已删除值,(逻辑删除下有效)
String0逻辑未删除值,(逻辑删除下有效)
com.baomidou.mybatisplus.annotation.FieldStrategyNOT_NULL字段验证策略之 insert,在 insert 的时候的字段验证策略
com.baomidou.mybatisplus.annotation.FieldStrategyNOT_NULL字段验证策略之 update,在 update 的时候的字段验证策略
com.baomidou.mybatisplus.annotation.FieldStrategyNOT_NULL字段验证策略之 select,在 select 的时候的字段验证策略既 wrapper 根据内部 entity 生成的 where 条件
都基于InnerInterceptor接口实现功能
@Configuration
@MapperScan("scan.your.mapper.package")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
return interceptor;
}
}
//查询条件
LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
//分页配置 页数 每页大小
IPage<User> userPage = new Page<>(1, 20);
userMapper.selectPage(userPage,userLambdaQueryWrapper);
//获取数据
List<User> records = userPage.getRecords();
自定义sql时将mapper接口返回值改为IPage,并将该参数传入,按上面用法使用
IPage<User> selectByName(IPage<User> page,String name);